package roborally.Pieces;

import roborally.util.MoveOutOfBoardException;
import be.kuleuven.cs.som.annotate.*;
/**
 * This is an enumeration of possible orientations of a GamePieceWithOrientation. Each enum has an integer representing it and a String representation.
 * @version 3.0
 * @author Bart Opsomer 2de Bachelor Ingenieurswetenschappen Electrotechniek-Computerwetenschappen 
 * 		   Peter Opsomer 1ste Master Wiskundige IngenieursTechnieken
 */
@Value
public enum Orientation {
	UP(0,"Up") { public PositionWithOrientation getNextPosition(Position position) throws MoveOutOfBoardException {
		return PositionWithOrientation.getPWO(position.getX(), position.getY() -1, position.getBoard(), UP);
	} },
	RIGHT(1,"Right") { public PositionWithOrientation getNextPosition(Position position) throws MoveOutOfBoardException {
		return PositionWithOrientation.getPWO(position.getX()+1, position.getY(), position.getBoard(), RIGHT);
	} },
	DOWN(2,"Down") { public PositionWithOrientation getNextPosition(Position position) throws MoveOutOfBoardException {
		return PositionWithOrientation.getPWO(position.getX(), position.getY() +1, position.getBoard(), DOWN);
	} },
	LEFT(3,"Left") { public PositionWithOrientation getNextPosition(Position position) throws MoveOutOfBoardException {
		return PositionWithOrientation.getPWO(position.getX()-1, position.getY(), position.getBoard(), LEFT);
	} };
	// the 2 possible representations of the orientation, an int and a String
	private int orientation;
	private String name;
	/**
	 * Make a new Orientation as enum.
	 * 
	 * @param orientation the integer representing this Orientation
	 * @param name the name of the Orientation
	 * @post | new.toString().equals(name)
	 * @post | new.getInt() == orientation
	 */
	@Raw
	private Orientation(int orientation, String name){
		this.orientation = orientation;
		this.name = name;
	}
	/**
	 *  @return the integer representing this Orientation
	 */
	@Basic @Immutable
	public int getInt() {
		return this.orientation;
	}
	/**
	 * @return a String representation of this Orientation.
	 */
	@Immutable
	public String toString() {
		return name;
	}
	/**
	 * Give the next PositionWithOrientation if one moves from the given Position with this Orientation.
	 * 
	 * @param position the Position to start from
	 * @return |  result.getPosition().getManhattanDistanceTo(position) == 1
	 * @return |  forall(Orientation orientation : Orientation.values() )
	 * 		   |	orientation.getNextPosition(position).getPosition().equals(result.getPosition() ) == (orientation == this)
	 * @throws MoveOutOfBoardException
	 * 		   |  !Position.isValidPosition(result.getPosition().getX(), result.getPosition().getY(), result.getPosition().getBoard() )
	 */
	public abstract PositionWithOrientation getNextPosition(Position position)  throws MoveOutOfBoardException;
	/**
	 * Get the Orientation corresponding the given integer.
	 * 
	 * @param 	i the integer to convert to an Orientation
	 * @return 	| if (forall Orientation o : Orientation.values()) { (o.getInt() != Math.abs(i%4) } then
	 * 		   	|	result == null
	 * 		   	| else
	 * 		   	|	result.getInt() == Math.abs(i%4)
	 */
	public static Orientation getOrientationFromInt(int i) {
		int orient = Math.abs(i%4);
		for (Orientation o : Orientation.values()) {
			if (o.getInt() == orient) {
				return o;
			}
		}
		return null;
	}
	/**
	 * Get the Orientation corresponding a clockwise rotation of this Orientation.
	 * 
	 * @effect |  this.getOrientationDifference(result) == 1
	 */
	 @Immutable
	public Orientation turnClockwise() {
		switch (this) {
			case UP : return RIGHT;
			case RIGHT : return	DOWN;
			case DOWN : return LEFT;
			case LEFT : return UP;
		}
		throw new IllegalStateException();
	}
	/**
	 * Get the Orientation corresponding a counterclockwise rotation of this Orientation.
	 * 
	 * @effect |  this.getOrientationDifference(result) == -1
	 */
	@Immutable
	public Orientation turnCounterClockwise() {
		switch (this) {
		case UP : return LEFT;
		case RIGHT : return	UP;
		case DOWN : return RIGHT;
		case LEFT : return DOWN;
	}
	throw new IllegalStateException();
	}
	/**
	 * Return +1 if this Orientation has to turn clockwise to get to given Orientation, -1 if this has to turn counterclockwise to get to the
	 * given Orientation and +2 or -2 if this Orientation had to turn twice to get to the given Orientation, taking into account that 
	 * LEFT.getOrientationDifference(UP) == +1 and UP.getOrientationDifference(LEFT) == -1.
	 * 
	 * @param orientation the Orientation to get to
	 * @return | if(this == UP && orientation == LEFT) then
	 * 		   |	result = -1
	 *  	   | else if(this == LEFT && orientation == UP) then
	 * 		   |	result = 1
	 * 	       | else
	 * 		   |	result = orientation.getInt() - getInt()
	 */
	public int getOrientationDifference(Orientation orientation) {
		int orientationDifference = orientation.getInt() - getInt();
		if(orientationDifference == 3) { 
			orientationDifference = -1;
		}
		else if(orientationDifference == -3) {
			orientationDifference = 1;
		}
		return orientationDifference;
	}
}

